/* Copyright (C) 2021 Rain */

/* This file is part of XNIX. */

/* 
  XNIX is free software: you can redistribute it and/or modify 
  it under the terms of the GNU General Public License as published by 
  the Free Software Foundation, either version 3 of the License, or 
  (at your option) and later version. 
*/

/*
  XNIX is distributed in the hope that it will be useful, 
  but WITHOUT ANY WARRANTY; without even the implied warranty of 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 
  GNU General Public License for more details. 
*/

/*
  You should have received a copy of the GNU General Public License 
   along with XNIX. If not, see <https://www.gnu.org/licenses/>.  
*/



#include <kernel/init.h>
#include <kernel/errno.h>
#include <kernel/types.h>

#include <kernel/console.h>
#include <kernel/pci.h>
#include <kernel/mm.h>

#include <kernel/int.h>
#include <kernel/panic.h>

#include <kernel/block.h>
#include <arch/x86/paging.h>
#include <arch/io.h>

#include <lib/string.h>
#include <modules/ide.h>


void (* ide_intr) (void) = NULL;

#define HD0 	1
#define HD1 	2

int intr_dev = 0;

__io_t hda_base, hda_ctl;
__io_t hdb_base, hdb_ctl;

static char buffer[512] = {0};

__uint64_t hda_nsectors, hdb_nsectors;


errno_t ide_init(struct pci_header0 *hdr) {
	__io_t hd0_base, hd0_ctl;
	__io_t hd1_base, hd1_ctl;

	ide_info("PCI BAR0: \t%q\n", hdr->bar0);
	ide_info("PCI BAR1: \t%q\n", hdr->bar1);
	ide_info("PCI BAR2: \t%q\n", hdr->bar2);
	ide_info("PCI BAR3: \t%q\n", hdr->bar3);
	ide_info("PCI BAR4: \t%q\n", hdr->bar4);
	ide_info("PCI BAR5: \t%q\n", hdr->bar5);

	ide_info("irq nr: \t%b\n", hdr->irqnr);
	ide_info("program interface: %q\n", hdr->hdr.prog_if);

	if (hdr->hdr.prog_if & PROGIF_HD0MODE) {
		hd0_base = hdr->bar0 & PCI_IO_MASK;
		hd0_ctl = hdr->bar1 & PCI_IO_MASK;
	}

	else {
		hd0_base = 0x1f0;
		hd0_ctl = 0x3f6;
	}

	if (hdr->hdr.prog_if & PROGIF_HD1MODE) {
		hd1_base = hdr->bar2 & PCI_IO_MASK;
		hd1_ctl = hdr->bar3 & PCI_IO_MASK;
	}

	else {
		hd1_base = 0x170;
		hd1_ctl = 0x376;
	}

	hda_base = hd0_base;
	hda_ctl = hd0_ctl;

	hdb_base = hd1_base;
	hdb_ctl = hd1_ctl;

	ide_info("hda port: %w  hda control port: %w\n", hd0_base, hd0_ctl);
	ide_info("hdb port: %w  hdb control port: %w\n", hd1_base, hd1_ctl);

	set_irq(0x0e, do_ide_intr);
	set_irq(0x0f, do_ide_intr);

	ide_intr = do_ide_id;
	intr_dev = HD0;

	outb(0x00, hd0_ctl);
	
	outb(0x00, hd0_base + PORT_ERR);
	outb(0x00, hd0_base + PORT_NSECTORS);

	outb(0x00, hd0_base + PORT_ADDR0);
	outb(0x00, hd0_base + PORT_ADDR1);
	outb(0x00, hd0_base + PORT_ADDR2);

	outb(0x00, hd0_base + PORT_DEV);
	outb(ATACMD_ID, hd0_base + PORT_STAT);

	while (ide_intr) hlt();

	ide_info("ide_init: id completed. \n");

	ide_intr = do_ide_id;
	intr_dev = HD1;

	outb(0x00, hd1_ctl);

	outb(0x00, hd1_base + PORT_ERR);
	outb(0x00, hd1_base + PORT_NSECTORS);

	outb(0x00, hd1_base + PORT_ADDR0);
	outb(0x00, hd1_base + PORT_ADDR1);
	outb(0x00, hd1_base + PORT_ADDR2);

	outb(0x00, hd1_base + PORT_DEV);
	outb(ATACMD_ID, hd1_base + PORT_STAT);

	while (ide_intr) hlt();

	ide_info("ide_init: id completed. \n");

	return 0;
}


void do_ide_intr(void) {
	if (!ide_intr) {
		ide_info("unexcepted ide interrupt. \n");
	}

	else {
		ide_intr();
		ide_intr = NULL;
	}

	return;
}


void do_ide_id(void) {
	__io_t dev = intr_dev == HD0 ? hda_base : hdb_base;
	__uint16_t buffer[256] = {0};

	insw(dev + PORT_DATA, buffer, 256);

	ide_info("sectors count: %d. \n", *(__uint64_t *) (buffer + 100));

	return;
}


void do_ide_read(void) {
	__io_t dev = intr_dev == HD0 ? hda_base : hdb_base;

	insw(dev + PORT_DATA, &buffer, 256);

	ide_info("read completed. \n");

	return;
}


void do_ide_write(void) {
	ide_info("write completed. \n");

	return;
}



int ide_cmdout(int dev, int cmd, char *buffer, int nsect, __uint64_t lba) {
	if (nsect > 255) 
		panic("ide_cmdout: bad nsect. ");

	if (cmd != BLKREAD && cmd != BLKWRITE) 
		panic("ide_cmdout: bad cmd. ");

	intr_dev = dev;
	ide_intr = cmd == BLKREAD ? do_ide_read : do_ide_write;

	__io_t port = dev == HD0 ? hda_base : hdb_base;
	__io_t ctl = dev == HD0 ? hda_ctl : hdb_ctl;

	outb(0x00, ctl);

	wait_disk_not_busy(port);

	outb(0xe0, port + PORT_DEV);
	outb(0x00, port + PORT_ERR);

	outb(nsect, port + PORT_NSECTORS);
	outb(lba & 0xff, port + PORT_ADDR0);
	outb((lba >> 8) & 0xff, port + PORT_ADDR1);
	outb((lba >> 16) & 0xff, port + PORT_ADDR2);

	wait_disk_ready(port);

	outb(cmd == BLKREAD ? ATACMD_RD : ATACMD_WR, port + PORT_STAT);

	if (cmd == BLKWRITE) 
		outsw(port + PORT_DATA, buffer, 256);

	return 0;
}


